using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.Common;
using NUnit.Core;
using NUnitForVSTS.NUnitTest.Properties;
using System.Xml;
using Microsoft.VisualStudio.TestTools.Common.Xml;
using System.Diagnostics;
using System.Linq;

namespace NUnitForVSTS.NUnitTest
{
    /// <summary>
    /// Test element that represents a nunit test.
    /// This is serialized in the creation of a trx file.
    /// </summary>
    [Guid("D6AF2CAE-AD69-4f6b-94E9-14E2609E2389")]
    [Serializable]
    public class UnitTest : TestElement, IHostedTest, IXmlTestStoreCustom, IVerifiable, ITestElementResultTestPropertyProvider, IDataSourceBindingData //,IFastVisiblePropertyProvider
    {
        /// <summary>
        /// Test Type which defines Nunit Tests
        /// </summary>
        public static readonly TestType NUnitTestType = new TestType(new Guid("D6AF2CAE-AD69-4f6b-94E9-14E2609E2389"));

        /// <summary>
        /// Location where the Test was created from.
        /// </summary>
        [PersistenceElementName("codeBase")]
        private string _codeBase;

        /// <summary>
        /// Initializes a new instance of the <see cref="UnitTest"/> class.
        /// </summary>
        private UnitTest(): base()
        {
            this.IsModified = true;
            FullyQualifiedClassName = string.Empty;
            CodeBase = string.Empty;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UnitTest"/> class.
        /// Clone method
        /// </summary>
        /// <param name="copy">The copy.</param>
        private UnitTest(UnitTest copy)
            : base(copy)
        {
            _codeBase = copy._codeBase;
            FullyQualifiedClassName = copy.FullyQualifiedClassName;
            DataTableName = copy.DataTableName;
            DataProviderName = copy.DataProviderName;
            DataConnectionString = copy.DataConnectionString;
            //_testMethod = copy._testMethod;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UnitTest"/> class.
        /// </summary>
        /// <param name="test">The test.</param>
        /// <param name="storage">The storage location</param>
        public UnitTest(ITest test, string storage)
            : base(test == null || test.TestName == null ? null : test.TestName.Name, string.Empty)
        {
            Check.NotNull(test, "test");
            Check.NotNull(test.TestName, "test.TestName");
            Check.NotNull(storage, "storage");

            string methodName = test.TestName.Name ?? string.Empty;
            int methodIndex = methodName.LastIndexOf('.');
            if (methodIndex > -1)
            {
                methodName = methodName.Substring(methodIndex + 1);
            }
            Name = methodName;

            int index = test.TestName.FullName.LastIndexOf('.');
            if (index > -1)
            {
                FullyQualifiedClassName = test.TestName.FullName.Substring(0, index);
            }
            else
            {
                FullyQualifiedClassName = string.Empty;
            }

            _codeBase = storage;
            m_id = new TestId(HashUtility.GuidFromString(test.TestName.FullName));

            Storage = storage;
        }

        /// <summary>
        /// Gets the adapter. 
        /// this links the adapter to the unittest type
        /// </summary>
        /// <value>The adapter.</value>
        public override string Adapter
        {
            get { return typeof(NUnitTestAdapter).AssemblyQualifiedName; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance can be aggregated.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can be aggregated; otherwise, <c>false</c>.
        /// </value>
        public override bool CanBeAggregated
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is load test candidate.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is load test candidate; otherwise, <c>false</c>.
        /// </value>
        public override bool IsLoadTestCandidate
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Clones this instance.
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return Copy;
        }

        /// <summary>
        /// Gets the controller plugin.
        /// </summary>
        /// <value>The controller plugin.</value>
        public override string ControllerPlugin
        {
            get { return null; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [read only].
        /// Needed for loading in tfs
        /// </summary>
        public override bool ReadOnly
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the type of the test.
        /// </summary>
        /// <value>The type of the test.</value>
        public override TestType TestType
        {
            get { return NUnitTestType; }
        }

        /// <summary>
        /// Gets a copy of this class
        /// </summary>
        /// <value>The copy.</value>
        public override ITestElement Copy
        {
            get
            {
                return new UnitTest(this);
            }
        }

        /// <summary>
        /// Gets the name of the fully qualified class.
        /// </summary>
        /// <value>The name of the fully qualified class.</value>
        [GroupingProperty, HelpKeyword("vs.test.unit.properties.fullclassname"), PropertyWindow, UserVisibleProperty("{B85E5129-BAAB-4ea1-8F05-E37BC6E989B9}"), TestCaseManagementDisplayName(typeof(Resources), "UnitTest_FullyQualifiedClassName"), LocalizedDescription(typeof(Resources), "UnitTest_FullyQualifiedClassName_PropertyDescription")]
        public string FullyQualifiedClassName
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the code base.
        /// </summary>
        /// <value>The code base.</value>
        public string CodeBase
        {
            get { return _codeBase; }
            private set { _codeBase = value;}
        }

        ///// <summary>
        ///// Gets the name of the class type.
        ///// </summary>
        ///// <value>The name of the class type.</value>
        //public string ClassTypeName
        //{
        //    get;
        //    private set;
        //}

        /// <summary>
        /// Gets the name of the class.
        /// </summary>
        /// <value>The name of the class.</value>
        [PropertyWindow, HelpKeyword("vs.test.unit.properties.classname"), UserVisibleProperty("{8343AD7E-406C-49ab-9D78-ED8A840C55B0}"), TestCaseManagementDisplayName(typeof(Resources), "UnitTest_ClassName"), LocalizedDescription(typeof(Resources), "UnitTest_ClassName_PropertyDescription"), GroupingProperty]
        public string ClassName
        {
            get
            {
                string fullyQualifiedClassName = FullyQualifiedClassName;
                int num = fullyQualifiedClassName.LastIndexOf('.');
                if (num > 0)
                {
                    return fullyQualifiedClassName.Substring(num + 1);
                }
                return fullyQualifiedClassName;
            }
        }

        /// <summary>
        /// Gets the namespace.
        /// </summary>
        /// <value>The namespace.</value>
        [UserVisibleProperty("{B42AE199-8A19-4806-B5CD-6B5A06282FDC}"), HelpKeyword("vs.test.unit.properties.namespace"), GroupingProperty, TestCaseManagementDisplayName(typeof(Resources), "UnitTest_Namespace"), LocalizedDescription(typeof(Resources), "UnitTest_Namespace_PropertyDescription"), PropertyWindow]
        public string Namespace
        {
            get
            {
                string fullyQualifiedClassName = FullyQualifiedClassName;
                int length = fullyQualifiedClassName.LastIndexOf('.');
                if (length > 0)
                {
                    return fullyQualifiedClassName.Substring(0, length);
                }
                return string.Empty;
            }
        }

        /// <summary>
        /// Gets the human readable id.
        /// </summary>
        /// <value>The human readable id.</value>
        [PropertyWindow, DefaultValue(""), TestCaseManagementDisplayName(typeof(Resources), "TestElement_HumanReadableId"), UserVisibleProperty("{A9405AE6-9AC6-4f0e-A03F-7AFE45F6FDB7}")]
        public override string HumanReadableId
        {
            get { return FullyQualifiedClassName + "." + Name; }
        }

        /// <summary>
        /// Gets or sets the name of the test
        /// </summary>
        /// <value>The name.</value>
        [UserVisibleProperty("{A9405AE6-9AC6-4f0e-A03F-7AFE45F6FCB7}"), PropertyWindow, ReadOnly(true), DefaultValue(""), TestCaseManagementDisplayName(typeof(Resources), "TestElement_Name")]
        public override string Name
        {
            get { return base.Name; }
            set { base.Name = value; }
        }

        /// <summary>
        /// Saves the specified element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="parameters">The parameters.</param>
        public override void Save(System.Xml.XmlElement element, Microsoft.VisualStudio.TestTools.Common.Xml.XmlTestStoreParameters parameters)
        {
            base.Save(element, parameters);
            try
            {
                XmlDocument document = element.OwnerDocument;
                if (element.SelectNodes(".//TestMethod", new XmlNamespaceManager(document.NameTable)).Count == 0)
                {
                    XmlNode method = document.CreateNode(XmlNodeType.Element, "TestMethod", element.NamespaceURI);
                    XmlAttribute codeBase = document.CreateAttribute("codeBase");
                    codeBase.Value = this.CodeBase;
                    method.Attributes.Append(codeBase);
                    XmlAttribute adapterTypeName = document.CreateAttribute("adapterTypeName");
                    adapterTypeName.Value = this.Adapter;
                    method.Attributes.Append(adapterTypeName);
                    XmlAttribute className = document.CreateAttribute("className");
                    className.Value = this.FullyQualifiedClassName;
                    method.Attributes.Append(className);
                    XmlAttribute name = document.CreateAttribute("name");
                    name.Value = this.Name;
                    method.Attributes.Append(name);
                    element.AppendChild(method);
                }
            }
            catch
                (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
                Trace.WriteLine(e.Message);
                Trace.WriteLine(e.StackTrace);
            }
        }

        /// <summary>
        /// Loads the specified element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="parameters">The parameters.</param>
        public override void Load(XmlElement element, Microsoft.VisualStudio.TestTools.Common.Xml.XmlTestStoreParameters parameters)
        {
            Trace.WriteLine("NUNITELEMENT: BEFORE LOAD");
            XmlDocument document = element.OwnerDocument;
            foreach (XmlNode node in element.SelectNodes(".//TestMethod", new XmlNamespaceManager(document.NameTable)))
            {
                XmlAttribute codeBase = node.Attributes["codeBase"];
                if (codeBase != null)
                {
                    this.CodeBase = codeBase.InnerText;
                }
                XmlAttribute className = node.Attributes["className"];
                if (className != null)
                {
                    this.FullyQualifiedClassName = className.InnerText;
                }
                XmlAttribute name = node.Attributes["className"];
                if (name != null)
                {
                    this.Name = name.InnerText;
                }
                element.RemoveChild(node);
            }
            base.Load(element, parameters);
            Trace.WriteLine("NUNITELEMENT: AFTER LOAD");
        }

        #region IXmlTestStoreCustom Members

        /// <summary>
        /// Gets the name of the element.
        /// </summary>
        /// <value>The name of the element.</value>
        public string ElementName
        {
            get { return "UnitTest"; }
        }

        /// <summary>
        /// Gets the namespace URI.
        /// </summary>
        /// <value>The namespace URI.</value>
        public string NamespaceUri
        {
            get
            {
                return Namespace;
            }
        }

        #endregion

        #region Interfaces which handle refresh and display in visual studio
        #region IFastVisiblePropertyProvider Members
        /*
        /// <summary>
        /// Fills the data row within the mstest framework.
        /// </summary>
        /// <param name="row">The row.</param>
        /// <remarks>there are two kinds of test: Managed an unmanaged. 
        /// Managed tests in the framework, are tests which have their own file. 
        /// Managed tests are persisted by using the Save method on the TIP. 
        /// Unmanaged tests, are tests which are derived on generated files (like dll's). 
        /// These kinds of tests (Like Nunit for VS tests) are not persisted trough the Tip, but need to update the metadata in the TMI.
        /// The update of the internal TMI table is done by implementing IFastVisiblePropertyProvider</remarks>
        public void FillDataRow(System.Data.DataRow row)
        {
            Trace.WriteLine("NUNITELEMENT: BEFORE FILLROW");
            UnitTest element = row["object_column"] as UnitTest;
            if (element == null)
            {
                row["object_column"] = Clone();
                base.FillDataRow(row, string.Empty);
            }
            
            //The fully qualified name is not persisted at this moment. This makes the visual studio refresh drop the tests.
            //This happens because the Fully qualified name is compared with the persisted name (which we cant control).
            //ChangeValue(row, "FullyQualifiedClassName", this.FullyQualifiedClassName);
            ChangeValue(row, "ClassName", this.ClassName);
            ChangeValue(row, "Namespace", this.Namespace);
            ChangeValue(row, "HumanReadableId", this.HumanReadableId);
            ChangeValue(row, "Owner", this.Owner);
            ChangeValue(row, "CreatedByUI", this.CreatedByUI);
            ChangeValue(row, "SolutionName", this.SolutionName);
            ChangeValue(row, "Enabled", this.Enabled);
            ChangeValue(row, "ProjectData", this.ProjectData);
            ChangeValue(row, "IsRunnable", this.IsRunnable);
            //These Values are part of Professional version and higher.
            //However we cant access the license manager or the helper.
            //ChangeValue(row, "CssProjectStructure", this.CssProjectStructure);
            //ChangeValue(row, "CssIteration", this.CssIteration);
            //ChangeValue(row, "WorkItemIdsViewable", this.WorkItemIdsViewable);
            ChangeValue(row, "Name", this.Name);
            ChangeValue(row, "TestType", this.TestType);
            //ChangeValue(row, "TimeoutVisible", this.TimeoutVisible);
            ChangeValue(row, "CanBeAggregated", this.CanBeAggregated);
            ChangeValue(row, "IsAutomated", this.IsAutomated);
            ChangeValue(row, "ProjectName", this.ProjectName);
            ChangeValue(row, "Priority", this.Priority);
            ChangeValue(row, "ErrorMessageForNonRunnable", this.ErrorMessageForNonRunnable);
            ChangeValue(row, "ProjectRelativePath", this.ProjectRelativePath);
            ChangeValue(row, "SourceFileName", this.SourceFileName);
            ChangeValue(row, "Storage", this.Storage);
            ChangeValue(row, "Description", this.Description);

            Trace.WriteLine("NUNITELEMENT: AFTER FILLROW");
        }

        /// <summary>
        /// Changes the value of the row.
        /// </summary>
        /// <param name="row">The row.</param>
        /// <param name="parameter">The parameter.</param>
        /// <param name="value">The value.</param>
        private static void ChangeValue<T>(System.Data.DataRow row, string parameter, T value)
        {
            if (value != null)
            {
                object read = row[parameter];
                if (read is DBNull)
                {
                    row[parameter] = value;
                }
                else if(!string.Equals(read.ToString(), value.ToString(), StringComparison.Ordinal))
                {
                    row[parameter] = value;
                }
            }
        }
        */
        #endregion 
        #endregion

        #region IDataSourceBindingData Members

        /// <summary>
        /// Gets or sets the data connection string.
        /// </summary>
        /// <value>The data connection string.</value>
        public string DataConnectionString
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the name of the data provider.
        /// </summary>
        /// <value>The name of the data provider.</value>
        public string DataProviderName
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the name of the data table.
        /// </summary>
        /// <value>The name of the data table.</value>
        public string DataTableName
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the test element.
        /// </summary>
        /// <value>The test element.</value>
        public ITestElement TestElement
        {
            get { return this; }
        }

        #endregion
    }
}
